package cn.stu.system.aspect;

import cn.stu.common.dto.UsersDto;
import cn.stu.common.utils.UserHolder;
import cn.stu.framework.entity.Log;
import cn.stu.framework.service.LogService;
import cn.stu.system.annotation.LogAnnotation;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 对数纵横比
 * desc: 日志切面类
 *
 * @author gaorimao
 * @since 2021-3-14
 */
@Aspect
@Component
@AllArgsConstructor
@Log4j2
public class LogAspect {
    private LogService logService;
    private HttpServletRequest request;
    private ObjectMapper objectMapper;


    /**
     * 扔完球后做
     * 异常通知 用于拦截service层记录异常日志
     *
     * @param joinPoint 连接点
     * @param e         E
     */
    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //Log入库
        // 操作数据库日志表
        Log l = new Log();
        l.setLogId(IdWorker.getId());
        Method method = getMethod(joinPoint);
        //触发人
        UsersDto user = UserHolder.getUser();
        if (user != null) {
            log.info("[log] loginUserId : {}", user.getUserId());
            l.setUserId(user.getUserId());
        }
        l.setRequestIp(request.getRemoteHost());
        l.setRequestUrl(request.getRequestURI());
        l.setRequestType(request.getMethod());
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        l.setAction(classMethod + "出现异常");
        l.setRequestMethod(classMethod);
        l.setErrorMessage(e.toString());
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = discoverer.getParameterNames(method);
        if (args != null && paramNames != null) {
            StringBuilder params = new StringBuilder();
            try {
                params = handleParams(params, args, Arrays.asList(paramNames));
            } catch (JsonProcessingException ee) {
                log.error("[LogAspect] setSysLogInfo handleParams error = {}", ee.getMessage());
            }
            l.setRequestParams(params.toString());
        }
        logService.insert(l);
    }

    @Pointcut("@annotation(cn.stu.system.annotation.LogAnnotation)")
    public void pointcut() {
    }

    /**
     * desc: 前置通知，记录controller层操作
     *
     * @param joinPoint 切入点
     */
    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) {
        try {
            handleLog(joinPoint);
        } catch (Exception e) {
            log.error("[LogAspect] doBefore error ={} ", e.getMessage());
        }
    }

    /**
     * desc: 通过反射获取日志信息，存入数据库
     *
     * @param joinPoint 切点
     */
    private void handleLog(final JoinPoint joinPoint) {
        // 获得注解
        Method method = getMethod(joinPoint);
        LogAnnotation logAnnotation = getAnnotationLog(method);
        if (logAnnotation == null) {
            return;
        }
        Log l = setLogInfo(joinPoint, method, logAnnotation);
        log.info(" {}", JSON.toJSONString(l));
        saveLog(l);
    }

    /**
     * 保存日志
     *
     * @param log 日志
     */
    @Async
    public void saveLog(Log log) {
        logService.save(log);
    }


    /**
     * 设置日志信息
     * desc: 初始化日志信息，存入数据库
     *
     * @param joinPoint     连接点
     * @param method        方法
     * @param logAnnotation 测井批注
     * @return SysLog
     */
    private Log setLogInfo(JoinPoint joinPoint, Method method, LogAnnotation logAnnotation) {
        // 操作数据库日志表
        Log l = new Log();
        l.setLogId(IdWorker.getId());
        l.setAction(logAnnotation.value());
        //触发人
        UsersDto user = UserHolder.getUser();
        log.info("[log] loginUserId : {}", user.getUserId());
        l.setUserId(user.getUserId());
        l.setRequestIp(request.getRemoteHost());
        l.setRequestUrl(request.getRequestURI());
        l.setRequestType(request.getMethod());
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        l.setRequestMethod(classMethod);
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = discoverer.getParameterNames(method);
        if (args != null && paramNames != null) {
            StringBuilder params = new StringBuilder();
            try {
                params = handleParams(params, args, Arrays.asList(paramNames));
            } catch (JsonProcessingException e) {
                log.error("[LogAspect] setSysLogInfo handleParams error ={}", e.getMessage());
                l.setErrorMessage(e.toString());
            }
            l.setRequestParams(params.toString());
        }
        return l;
    }


    /**
     * 获取批注日志
     * dsec: 是否存在注解，如果存在就获取
     *
     * @param method 方法
     * @return {@link LogAnnotation}
     */
    private LogAnnotation getAnnotationLog(Method method) {
        if (method != null) {
            return method.getAnnotation(LogAnnotation.class);
        }
        return null;
    }

    /**
     * GET方法
     * desc: 通过反射或缺当前请求方法
     *
     * @param joinPoint 切点
     * @return Method
     */
    private Method getMethod(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method;
        }
        return null;
    }

    /**
     * 控制柄参数
     * desc: 处理请求参数
     *
     * @param params     帕拉姆斯
     * @param args       ARGS
     * @param paramNames 参数名称
     * @return {@link StringBuilder}
     * @throws JsonProcessingException JSON处理异常
     */
    private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) throws JsonProcessingException {
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Map) {
                Set set = ((Map) args[i]).keySet();
                List list = new ArrayList();
                List paramList = new ArrayList<>();
                for (Object key : set) {
                    list.add(((Map) args[i]).get(key));
                    paramList.add(key);
                }
                return handleParams(params, list.toArray(), paramList);
            } else {
                if (args[i] instanceof Serializable) {
                    Class<?> aClass = args[i].getClass();
                    try {
                        aClass.getDeclaredMethod("toString", new Class[]{null});
                        // 如果不抛出NoSuchMethodException 异常则存在 toString 方法 ，安全的writeValueAsString ，否则 走 Object的 toString方法
                        params.append("  ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i]));
                    } catch (NoSuchMethodException e) {
                        params.append("  ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i].toString()));
                    }
                } else if (args[i] instanceof MultipartFile) {
                    MultipartFile file = (MultipartFile) args[i];
                    params.append("  ").append(paramNames.get(i)).append(": ").append(file.getName());
                } else {
                    params.append("  ").append(paramNames.get(i)).append(": ").append(args[i]);
                }
            }
        }
        return params;
    }
}
